package com.sneakxy.mybatis.commons.repository.query.executor;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.binding.MapperMethod.ParamMap;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.scripting.xmltags.DynamicSqlSource;
import org.apache.ibatis.scripting.xmltags.MixedSqlNode;
import org.apache.ibatis.scripting.xmltags.SqlNode;
import org.apache.ibatis.scripting.xmltags.TextSqlNode;
import org.apache.ibatis.scripting.xmltags.WhereSqlNode;

import com.sneakxy.mybatis.commons.orm.MybatisEntityManager;
import com.sneakxy.mybatis.commons.orm.generation.GenerationManager;
import com.sneakxy.mybatis.commons.orm.mapping.MybatisColumn;
import com.sneakxy.mybatis.commons.orm.mapping.MybatisColumnSqlNode;
import com.sneakxy.mybatis.commons.repository.query.domain.Criteria;
import com.sneakxy.mybatis.commons.repository.query.domain.CriteriaSqlNode;
import com.sneakxy.mybatis.commons.repository.query.parser.MybatisPartTree;

/**
 * @author 潜行的青衣
 */
public class CriteriaUpdateRepositoryExecutor<T> extends AbstractRepositoryExecutor<T> {

	private String updateStatement;
	
	/**
	 * @param entityManager
	 * @param loader
	 * @param method
	 * @param type
	 */
	public CriteriaUpdateRepositoryExecutor(MybatisEntityManager entityManager, Class<?> loader, String method, Class<?>...type) {
		super(entityManager, loader, method, type);
	}
	
	/**
	 * @param entityManager
	 */
	public CriteriaUpdateRepositoryExecutor(MybatisEntityManager entityManager, Method method) {
		super(entityManager, method);
	}

	/* (non-Javadoc)
	 * @see com.sneakxy.mybatis.commons.repository.query.executor.RepositoryExecutor#execute(java.lang.Object[])
	 */
	@SuppressWarnings("unchecked")
	@Override
	public T execute(Object... parameters) {
		Object entity = parameters[0];
		this.defaultUpdate(entity, parameters[1], parameters[2]);
		return (T) entity;
	}

	/* (non-Javadoc)
	 * @see com.sneakxy.mybatis.commons.repository.query.executor.AbstractRepositoryExecutor#onBuildStatment()
	 */
	@Override
	protected void onBuildStatment() {
		this.updateStatement = this.getEntityManager().getNamespacedId("_defaultCriteriaUpdate");
		this.buildUpdateStatement();
	}

	@SuppressWarnings("unchecked")
	protected Object defaultUpdate(Object... parameters) {
		Object entity = parameters[0];
		Map<String, Object> parameterObject = (Map<String, Object>) this.createParameterObject(parameters, false);
		this.getEntityManager().getSqlSessionTemplate().update(updateStatement, parameterObject);
		return entity;
	}
	
	protected void buildUpdateStatement() {
		GenerationManager generationManager = this.getEntityManager().getGenerationManager();
		StringBuilder sqlBuilder = new StringBuilder();
		List<SqlNode> contents = new ArrayList<>();
		List<MybatisColumn> columns = this.getEntityManager().getEntityColumns(StringUtils.join(MybatisPartTree.GENERIC_NAME_PREFIX, "1"));
		sqlBuilder.append("UPDATE ${").append(MybatisPartTree.GENERIC_NAME_PREFIX).append("2} SET ");
		contents.add(new TextSqlNode(sqlBuilder.toString()));
		WhereSqlNode whereSqlNode = new WhereSqlNode(this.getEntityManager().getConfiguration(), new CriteriaSqlNode(this.getEntityManager().getConfiguration(), getCriteriaIndex()));
		contents.add(new MybatisColumnSqlNode(generationManager, columns, ","));
		contents.add(whereSqlNode);
		
		String id = this.updateStatement;
		SqlCommandType sqlCommandType  = SqlCommandType.UPDATE;
		SqlSource sqlSource = new DynamicSqlSource(this.getEntityManager().getConfiguration(), new MixedSqlNode(contents));
		this.getEntityManager().addMappedStatement(id, sqlSource, sqlCommandType, ParamMap.class, Integer.class);
	}

	public int getCriteriaIndex() {
		int index = 1;
		for(Class<?> type:this.getMethod().getParameterTypes()) {
			if(Criteria.class.isAssignableFrom(type)) {
				break;
			}
			index ++;
		}
		return index;
	}
	
}
